package com.atguigu.srb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.common.exception.BusinessException;
import com.atguigu.srb.core.enums.LendStatusEnum;
import com.atguigu.srb.core.enums.ReturnMethodEnum;
import com.atguigu.srb.core.enums.TransTypeEnum;
import com.atguigu.srb.core.hfb.HfbConst;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.mapper.*;
import com.atguigu.srb.core.pojo.bo.TransFlowBO;
import com.atguigu.srb.core.pojo.entity.*;
import com.atguigu.srb.core.pojo.vo.BorrowInfoApprovalVO;
import com.atguigu.srb.core.pojo.vo.BorrowerDetailVO;
import com.atguigu.srb.core.service.*;
import com.atguigu.srb.core.utils.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author tjf
 * @since 2022-01-03
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Resource
    private BorrowerMapper borrowerMapper;
    @Resource
    private UserInfoMapper userInfoMapper;
    @Resource
    private UserAccountMapper userAccountMapper;
    @Resource
    private LendItemMapper lendItemMapper;

    private final DictService dictService;

    private final BorrowerService borrowerService;

    private final TransFlowService transFlowService;

    private final LendReturnService lendReturnService;

    private final LendItemReturnService lendItemReturnService;

    public LendServiceImpl(DictService dictService, BorrowerService borrowerService, TransFlowService transFlowService, LendReturnService lendReturnService, LendItemReturnService lendItemReturnService) {
        this.dictService = dictService;
        this.borrowerService = borrowerService;
        this.transFlowService = transFlowService;
        this.lendReturnService = lendReturnService;
        this.lendItemReturnService = lendItemReturnService;
    }

    @Override
    public void createLend(BorrowInfoApprovalVO borrowInfoApprovalVO, BorrowInfo borrowInfo) {
        Lend lend = new Lend();

        lend.setUserId(borrowInfo.getUserId());
        lend.setBorrowInfoId(borrowInfo.getId());
        lend.setLendNo(LendNoUtils.getLendNo());
        lend.setTitle(borrowInfoApprovalVO.getTitle());
        lend.setAmount(borrowInfo.getAmount());
        lend.setPeriod(borrowInfo.getPeriod());
        lend.setLendYearRate(borrowInfoApprovalVO.getLendYearRate().divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));
        lend.setServiceRate(borrowInfoApprovalVO.getServiceRate().divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        lend.setLowestAmount(new BigDecimal("100"));
        lend.setInvestAmount(new BigDecimal("0"));
        lend.setInvestNum(0);
        lend.setPublishDate(LocalDateTime.now());

        // 起始日期
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate lendStartDate = LocalDate.parse(borrowInfoApprovalVO.getLendStartDate(), dateTimeFormatter);
        lend.setLendStartDate(lendStartDate);
        LocalDate lendEndDate = lendStartDate.plusMonths(borrowInfo.getPeriod());
        lend.setLendEndDate(lendEndDate);

        lend.setLendInfo(borrowInfoApprovalVO.getLendInfo());

        // 平台预期收益 = 标的金额 * 平台预期收益率
        // 平台预期收益率 = 年化 / 12 * 期数
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, RoundingMode.DOWN);
        BigDecimal expectAmount = lend.getAmount().multiply(monthRate.multiply(new BigDecimal(lend.getPeriod())));
        lend.setExpectAmount(expectAmount);

        lend.setRealAmount(new BigDecimal("0"));
        lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());
        lend.setCheckTime(LocalDateTime.now());
        // 审核人
        lend.setCheckAdminId(1L);

        baseMapper.insert(lend);
    }

    @Override
    public List<Lend> selectList() {
        List<Lend> lendList = baseMapper.selectList(null);
        lendList.forEach(lend -> {
            String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod());
            String status = LendStatusEnum.getMsgByStatus(lend.getStatus());
            lend.getParam().put("returnMethod", returnMethod);
            lend.getParam().put("status", status);
        });
        return lendList;
    }

    @Override
    public Map<String, Object> getLendDetail(Long id) {
        // 组装lend对象
        Lend lend = baseMapper.selectById(id);
        if (lend == null) {
            throw new BusinessException("标的id不存在");
        }
        String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod());
        String status = LendStatusEnum.getMsgByStatus(lend.getStatus());
        lend.getParam().put("returnMethod", returnMethod);
        lend.getParam().put("status", status);

        // 获取borrowerVO
        LambdaQueryWrapper<Borrower> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Borrower::getUserId, lend.getUserId());
        Borrower borrower = borrowerMapper.selectOne(queryWrapper);
        BorrowerDetailVO borrowerDetailVO = borrowerService.getBorrowerDetailVOById(borrower.getId());

        // 组装map集合
        HashMap<String, Object> map = new HashMap<>();
        map.put("lend", lend);
        map.put("borrower", borrowerDetailVO);
        return map;
    }

    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalMonth, Integer returnMethod) {
        if (returnMethod.intValue() == ReturnMethodEnum.ONE.getMethod().intValue()) {
            return Amount1Helper.getInterestCount(invest, yearRate, totalMonth);
        } else if (returnMethod.intValue() == ReturnMethodEnum.TWO.getMethod().intValue()) {
            return Amount2Helper.getInterestCount(invest, yearRate, totalMonth);
        } else if (returnMethod.intValue() == ReturnMethodEnum.THREE.getMethod().intValue()) {
            return Amount3Helper.getInterestCount(invest, yearRate, totalMonth);
        } else {
            return Amount4Helper.getInterestCount(invest, yearRate, totalMonth);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void makeLoan(Long id) {
        // 获取标的信息
        Lend lend = baseMapper.selectById(id);
        // 调用汇付宝放款接口
        HashMap<String, Object> map = new HashMap<>();
        map.put("agentId", HfbConst.AGENT_ID);
        map.put("agentProjectCode", lend.getLendNo());
        map.put("agentBillNo", LendNoUtils.getLoanNo());
        // 月年化
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, RoundingMode.HALF_UP);
        // 月年化 * 已投金额 * 投资时长
        BigDecimal realAmount = monthRate.multiply(lend.getInvestAmount()).multiply(new BigDecimal(lend.getPeriod()));
        map.put("mchFee", realAmount);
        map.put("timestamp", RequestHelper.getTimestamp());
        map.put("sign", RequestHelper.getSign(map));
        JSONObject result = RequestHelper.sendRequest(map, HfbConst.MAKE_LOAD_URL);
        log.info("放款结果：" + result.toJSONString());

        // 放款失败
        if (!"0000".equals(result.getString("resultCode"))) {
            throw new BusinessException(result.getString("resultMsg"));
        }

        // 放款成功

        // 标的状态和标的平台收益
        lend.setRealAmount(realAmount);
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());
        lend.setPaymentTime(LocalDateTime.now());
        baseMapper.updateById(lend);

        // 给借款账号转入金额
        Long userId = lend.getUserId();
        UserInfo userInfo = userInfoMapper.selectById(userId);
        String bindCode = userInfo.getBindCode();
        String voteAmt = result.getString("voteAmt");
        userAccountMapper.updateAccount(bindCode, new BigDecimal(voteAmt), null);

        // 增加借款交易流水
        TransFlowBO transFlowBO = new TransFlowBO(
                result.getString("agentBillNo"),
                bindCode,
                new BigDecimal(voteAmt),
                TransTypeEnum.BORROW_BACK,
                "项目放款，项目编号：" + lend.getLendNo() + "，项目名称：" + lend.getTitle()
        );
        transFlowService.saveTransFlow(transFlowBO);

        // 解冻并扣除投资人资金
        LambdaQueryWrapper<LendItem> lendItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
        lendItemLambdaQueryWrapper.eq(LendItem::getLendId, id);
        List<LendItem> lendItemList = lendItemMapper.selectList(lendItemLambdaQueryWrapper);
        lendItemList.forEach(lendItem -> {
            Long investUserId = lendItem.getInvestUserId();
            UserInfo investUserInfo = userInfoMapper.selectById(investUserId);
            String investBindCode = investUserInfo.getBindCode();
            BigDecimal investAmount = lendItem.getInvestAmount();
            userAccountMapper.updateAccount(investBindCode, null, investAmount.negate());

            // 增加投资人交易流水
            TransFlowBO investTransFlowBO = new TransFlowBO(
                    LendNoUtils.getTransNo(),
                    investBindCode,
                    investAmount,
                    TransTypeEnum.INVEST_UNLOCK,
                    "项目放款，冻结资金转出，项目编号：" + lend.getLendNo() + "，项目名称：" + lend.getTitle()
            );
            transFlowService.saveTransFlow(investTransFlowBO);
        });


        // 生成借款人还款计划和出借人回款计划
        repaymentPlan(lend, lendItemList);
    }

    // 还款计划
    private void repaymentPlan(Lend lend, List<LendItem> lendItemList) {
        // 创建还款计划列表
        List<LendReturn> lendReturnList = new ArrayList<>(lend.getPeriod());
        // 根据还款期限生成还款计划（for period)
        for (int i = 1; i <= lend.getPeriod(); i++) {
            // 创建还款计划对象
            // 填充基本属性
            LendReturn lendReturn = new LendReturn();
            lendReturn.setLendId(lend.getId());
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            lendReturn.setUserId(lend.getUserId());
            lendReturn.setAmount(lend.getAmount());
            lendReturn.setBaseAmount(lend.getInvestAmount());
            lendReturn.setCurrentPeriod(i);
            lendReturn.setLendYearRate(lend.getLendYearRate());
            lendReturn.setReturnMethod(lend.getReturnMethod());
            // 还款计划中三项，回款计划中对应的三项和，需先生成回款计划
            // lendReturn.setPrincipal();
            // lendReturn.setInterest();
            // lendReturn.setTotal();
            lendReturn.setFee(new BigDecimal("0"));
            lendReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));
            lendReturn.setOverdue(false);
            // 判断是否为最后一个月
            lendReturn.setLast(i == lend.getPeriod());
            // 设置还款状态
            lendReturn.setStatus(0);
            // 将还款对象加入还款计划列表
            lendReturnList.add(lendReturn);
        }
        // 批量保存还款计划
        lendReturnService.saveBatch(lendReturnList);

        // 获取lendReturnList中还款计划id对应的map，key是期数，val是还款款计划id
        Map<Integer, Long> lendReturnMap = lendReturnList.stream().collect(Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId));

        // 获取所有投资者，生成回款计划
        List<LendItemReturn> lendItemReturnAllList = new ArrayList<>();
        // 遍历投资记录，生成回款记录列表
        for (LendItem lendItem : lendItemList) {
            List<LendItemReturn> lendItemReturnList = returnInvest(lendItem, lendReturnMap, lend);
            lendItemReturnAllList.addAll(lendItemReturnList);
        }

        // 遍历还款记录列表
        for (LendReturn lendReturn : lendReturnList) {
            // 遍历出当期相应的回款计划
            // 设置本金
            BigDecimal sumPrincipal = lendItemReturnAllList.stream()
                    .filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getPrincipal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            lendReturn.setPrincipal(sumPrincipal);

            // 设置利息
            BigDecimal sumInterest = lendItemReturnAllList.stream()
                    .filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getInterest)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            lendReturn.setInterest(sumInterest);

            // 设置本息
            lendReturn.setTotal(sumPrincipal.add(sumInterest));

            lendReturnService.updateById(lendReturn);
        }
    }

    // 回款计划
    public List<LendItemReturn> returnInvest(LendItem lendItem, Map<Integer, Long> lendReturnMap, Lend lend) {
        // 投资金额
        BigDecimal investAmount = lendItem.getInvestAmount();
        // 年化利率
        BigDecimal lendYearRate = lendItem.getLendYearRate();
        // 投资期数
        Integer period = lend.getPeriod();

        Map<Integer, BigDecimal> mapInterest;
        Map<Integer, BigDecimal> mapPrincipal;

        // 根据还款方式计算本金和利息
        if (lend.getReturnMethod().intValue() == ReturnMethodEnum.ONE.getMethod().intValue()) {
            mapInterest = Amount1Helper.getPerMonthInterest(investAmount, lendYearRate, period);
            mapPrincipal = Amount1Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
        } else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.TWO.getMethod().intValue()) {
            mapInterest = Amount2Helper.getPerMonthInterest(investAmount, lendYearRate, period);
            mapPrincipal = Amount2Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
        } else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.THREE.getMethod().intValue()) {
            mapInterest = Amount3Helper.getPerMonthInterest(investAmount, lendYearRate, period);
            mapPrincipal = Amount3Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
        } else {
            mapInterest = Amount4Helper.getPerMonthInterest(investAmount, lendYearRate, period);
            mapPrincipal = Amount4Helper.getPerMonthPrincipal(investAmount, lendYearRate, period);
        }

        // 创建回款计划表
        List<LendItemReturn> lendItemReturnList = new ArrayList<>();
        for (int i = 1; i <= period; i++) {
            LendItemReturn lendItemReturn = new LendItemReturn();
            // 赋值
            lendItemReturn.setLendReturnId(lendReturnMap.get(i));
            lendItemReturn.setLendItemId(lendItem.getId());
            lendItemReturn.setLendId(lend.getId());
            lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
            lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
            lendItemReturn.setCurrentPeriod(i);
            lendItemReturn.setLendYearRate(lendYearRate);
            lendItemReturn.setReturnMethod(lend.getReturnMethod());
            // 本金、利息、本息
            // 最后一次本金计算
            if (lendItemReturnList.size() > 0 && i == period) {
                BigDecimal sumPrincipal = lendItemReturnList.stream().map(LendItemReturn::getPrincipal).reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal lastPrincipal = lendItem.getInvestAmount().subtract(sumPrincipal);
                lendItemReturn.setPrincipal(lastPrincipal);
            } else {
                BigDecimal principal = mapPrincipal.get(i);
                lendItemReturn.setPrincipal(principal);
            }
            BigDecimal interest = mapInterest.get(i);
            BigDecimal total = lendItemReturn.getPrincipal().add(interest);
            lendItemReturn.setInterest(interest);
            lendItemReturn.setTotal(total);
            lendItemReturn.setFee(new BigDecimal("0"));
            lendItemReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));
            lendItemReturn.setOverdue(false);
            lendItemReturn.setStatus(0);

            lendItemReturnList.add(lendItemReturn);
        }
        lendItemReturnService.saveBatch(lendItemReturnList);
        return lendItemReturnList;
    }
}
